package ru.nsu.expertsystem.chucknorris.xml_parser;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.ObjectName;

import org.simpleframework.xml.core.Persister;

import com.sun.org.apache.bcel.internal.generic.DMUL;

import ru.nsu.expertsystem.chucknorris.ConsoleLogger;
import ru.nsu.expertsystem.chucknorris.DataSource;
import ru.nsu.expertsystem.chucknorris.ChuckNorrisFactsGenerator;
import ru.nsu.expertsystem.chucknorris.Logger;
import ru.nsu.expertsystem.chucknorris.Object;
import ru.nsu.expertsystem.chucknorris.Predicate;
import ru.nsu.expertsystem.chucknorris.xml_parser.xml.XmlData;
import ru.nsu.expertsystem.chucknorris.xml_parser.xml.XmlObject;
import ru.nsu.expertsystem.chucknorris.xml_parser.xml.XmlPredicate;

public class XmlParser implements DataSource {
	private final Logger logger;
	private final String classNamePrefix;
	private Persister persister = new Persister();
	
	private Set<ru.nsu.expertsystem.chucknorris.Object> objects;
	private Collection<Predicate> predicates;
	
	public XmlParser(String classNamePrefix) {
		this(classNamePrefix, null);
	}
	
	public XmlParser(String classNamePrefix, Logger logger) {
		if(classNamePrefix == null)
			throw new IllegalArgumentException("null");
		this.classNamePrefix = classNamePrefix;
		this.logger = logger != null ? logger : new Logger() {
			public void worning(String message) {
			}
			public void information(String message) {
			}
			public void error(String message) {
			}
			public void error(String message, Throwable cause) {
			}
			public void worning(String message, Throwable cause) {
			}
			public void information(String message, Throwable cause) {
			}
		};
	}
	
	public void parse(InputStream inputStream) throws Exception {
		final XmlData xmlData = persister.read(XmlData.class, inputStream);
		final List<XmlObject> xmlObjects = xmlData.getObjects();
		final List<XmlPredicate> xmlPredicates = xmlData.getPredicates();
		final Map<String, Object> objects = instantiateObjects(xmlObjects);
		this.objects = new HashSet<Object>(objects.size());
		for(Object object : objects.values()) {
			this.objects.add(object);
		}
		this.predicates = instantiatePredicates(xmlPredicates, objects);
		predicates.toString();
	}
	
	@Override
	public Set<ru.nsu.expertsystem.chucknorris.Object> getObjects() {
		if(objects == null)
			return null;
		return Collections.unmodifiableSet(objects);
	}

	@Override
	public Collection<Predicate> getPredicates() {
		if(predicates == null)
			return predicates;
		return Collections.unmodifiableCollection(predicates);
	}
	
	private Map<String, Object> instantiateObjects(List<XmlObject> xmlObjects) {
		final HashMap<String, Object> objects = new HashMap<String, Object>(xmlObjects.size());
		for(XmlObject xmlObject : xmlObjects) {
			final String fullClassName = classNamePrefix + '.' + xmlObject.getClassName();
			final Class<?> clazz;
			try {
				clazz = Class.forName(fullClassName);
				final Constructor<Object> constructor = (Constructor<Object>) clazz.getConstructor(String.class);
				final Object object = constructor.newInstance(xmlObject.getName());
				
				objects.put(object.getName(), object);
			} catch (Exception e) {
				logger.worning("couldn't instantiate Object: " + xmlObject.getName(), e);
				continue;
			}
			
		}
		
		return objects;
	}
	
	private Collection<Predicate> instantiatePredicates(List<XmlPredicate> xmlPredicates, Map<String, Object> objects) {
		final ArrayList<Predicate> predicates = new ArrayList<Predicate>(xmlPredicates.size());
		loopPredicates: for(XmlPredicate xmlPredicate : xmlPredicates) {
			final String fullPredicateClassName = classNamePrefix + '.' + xmlPredicate.getClassName();
			final Class<Predicate> predicateClass;
			try {
				predicateClass = (Class<Predicate>)Class.forName(fullPredicateClassName);
			} catch (Exception e) {
				logger.worning("can not load class", e);
				continue loopPredicates;
			}
			final Predicate predicate;
			try {
				predicate = predicateClass.newInstance();
			} catch (Exception e) {
				logger.worning("class doesn't have public default construcrou: " + fullPredicateClassName);
				continue loopPredicates;
			}
			
			for(XmlPredicate.Attribute xmlAttribute : xmlPredicate.getAttributes()) {
				final String fieldName = xmlAttribute.getName();
				final String objectName = xmlAttribute.getObjectName();
				final Object object;
				if(xmlAttribute.getConstClassName() == null) {
					object = objects.get(objectName);
					if(object == null) {
						logger.worning("Object doesn't exist: " + objectName);
						continue loopPredicates;
					}
				} else {
					try {
						final String fullConstClassName = classNamePrefix + '.' + xmlAttribute.getConstClassName();
						final Class<? extends Object> constClass = (Class<? extends Object>)Class.forName(fullConstClassName);
						object = Object.getConstantByName(constClass, objectName);
					} catch (Exception e) {
						logger.worning("could not parse constant: " + fieldName , e);
						continue loopPredicates;
					}
					
				}
				try {
					setFielsOfTheNearestParent(predicate, fieldName, object);
				} catch (Exception e) {
					logger.worning("couldn't set field: " + fieldName, e);
					continue loopPredicates;
				}
			}
			predicates.add(predicate);
		}
		
		return predicates;
	}
	
	private static void setFielsOfTheNearestParent(Predicate predicate, String fieldName, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
		Class<?> clazz = predicate.getClass();
		Field field = null;
		for( ; clazz != Object.class ; ) {
			try {
				field = clazz.getDeclaredField(fieldName);
				break;
			} catch (Exception e) {
			}
			clazz = clazz.getSuperclass();
			
		}
		if(field == null) {
			throw new NoSuchFieldException(fieldName);
		}
		
		field.setAccessible(true);
		field.set(predicate, value);
	}
}
